/*
 * FlightDatabase.cpp
 *
 *  Created on: May 8, 2014
 *      Author: alexander
 */

#include "FlightDatabase.h"
#include <exception>
#include <iostream>
#include <fstream>

#include "Poco/StringTokenizer.h"
//#include "Poco/String.h"
//#include "Poco/DateTime.h"
//#include "Poco/Timespan.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/NumberParser.h"
#include "Poco/DateTimeParser.h"
#include "Poco/LocalDateTime.h"

#include "Poco/File.h"
#include "Poco/Path.h"

#include "Poco/NumberFormatter.h"
#include "Poco/Logger.h"
#include "Poco/SimpleFileChannel.h"
#include "Poco/AutoPtr.h"

bool FlightDatabase::isInstanciated = false;

FlightDatabase FlightDatabase::getInstance()
{
	if (isInstanciated == false)
	{
		isInstanciated = true;
		return FlightDatabase();
	}
	throw std::exception();
}

void FlightDatabase::findFlights(const std::string& where, int month)
{
	const int DEFAULT_SEARCH_RANGE = 180;

	const int FIRST_MONTH = Poco::DateTime::Months::JANUARY;
	const int LAST_MONTH = Poco::DateTime::Months::DECEMBER;
	if (month > LAST_MONTH)
	{
		std::cout << "Invalid month passed: " << month
				<< ". Expected value is in range [" << FIRST_MONTH << ", "
				<< LAST_MONTH << "]" << std::endl;
		return;
	}

	Poco::DateTime date;
	Poco::Timespan daySpan(Poco::Timespan::DAYS);
	int searchRange = DEFAULT_SEARCH_RANGE;
	if (month)
	{
		Poco::DateTime now;
		int year = (now.month() > month) ? now.year() + 1 : now.year();
		date = Poco::DateTime(year, month, 1);
		searchRange = Poco::DateTime::daysOfMonth(date.year(), date.month());
	}

	std::map<std::string, Flight> webFlights;

	for (int i = 0; i < searchRange; i++, date += daySpan)
	{
		std::string dateStr(Poco::DateTimeFormatter::format(date, "%Y-%m-%d"));
		Flight flight = Flight::obtainFlightFromWeb(where, dateStr);
		if (flight.isValid())
		{
			webFlights[dateStr] = flight;
		}
	}

	if (!webFlights.empty())
	{
		addFlights(where, webFlights);
	}
}

void FlightDatabase::addFlights(const std::string& where, const std::map<std::string, Flight>& newFlights)
{
//	Poco::LocalDateTime today;
	std::string todayStr(Poco::DateTimeFormatter::format(Poco::LocalDateTime(), "[%d-%m-%Y] "));
	try
	{
		std::map<std::string, Flight> flightsToDestination = flights.at(where);
		for (auto& newFlight : newFlights)
		{
			try
			{
				Flight currentFlight = flightsToDestination.at(newFlight.first);
				if (currentFlight.getPrice() != newFlight.second.getPrice())
				{
					Poco::AutoPtr<Poco::SimpleFileChannel> pChannel(new Poco::SimpleFileChannel);
					pChannel->setProperty("path", "!price_change_history");
					pChannel->setProperty("rotation", "5M");
					Poco::Logger::root().setChannel(pChannel);
					Poco::Logger& logger = Poco::Logger::get("Change Logger");

					std::string logStr = "(" + where + " on " + newFlight.first + "):  from " +
							Poco::NumberFormatter::format(currentFlight.getPrice(), 2) +
							currentFlight.getCurrency() + " to " +
							Poco::NumberFormatter::format(newFlight.second.getPrice(), 2) +
							newFlight.second.getCurrency();
					if (currentFlight.getPrice() > newFlight.second.getPrice())
					{
						logStr = todayStr + "FALL " + logStr;
					}
					else
					{
						logStr = todayStr + "RAISE " + logStr;
					}
					logger.information(logStr);

//					logger.information("Price for " + where + " on " + newFlight.first + " has changed:  was " +
//							Poco::NumberFormatter::format(currentFlight.getPrice(), 2) +
//							currentFlight.getCurrency() + ", now " +
//							Poco::NumberFormatter::format(newFlight.second.getPrice(), 2) +
//							newFlight.second.getCurrency());

					std::cout << "Change in price detected, see file \'price_change_history\'" << std::endl;

					flights[where][newFlight.first] = newFlight.second;
				}
			}
			catch (std::out_of_range& e)
			{
				flights[where][newFlight.first] = newFlight.second;
			}
		}
	}
	catch (std::out_of_range& e)
	{
		flights[where] = newFlights;
	}
}

void FlightDatabase::findCheapest(const std::string& where, int month) const
{
	for (auto& flight : flights)
	{
		if (where.length())
		{
			if (where.compare(flight.first))
			{
				continue;
			}
		}

		std::pair<std::string, Flight> cheapest;
		bool flag = false;
		for (auto& flightInfo : flight.second)
		{
			if (month)
			{
				int dummy;
				Poco::DateTime dt;
				Poco::DateTimeParser::parse(flightInfo.first, dt, dummy);
				if (dt.month() != month)
				{
					continue;
				}
			}
			if (!flag || flightInfo.second.getPrice() < cheapest.second.getPrice())
			{
				flag = true;
				cheapest = flightInfo;
			}
		}

		std::cout << "Cheapest ticket for " << flight.first << ":\n" << cheapest.first
				<< ": " << cheapest.second.getPrice() << cheapest.second.getCurrency() << std::endl;
	}
}

void FlightDatabase::findCheapest(int month) const
{
	const int FIRST_MONTH = Poco::DateTime::Months::JANUARY;
	const int LAST_MONTH = Poco::DateTime::Months::DECEMBER;
	if (month > LAST_MONTH)
	{
		std::cout << "Invalid month passed: " << month
				<< ". Expected value is in range [" << FIRST_MONTH << ", "
				<< LAST_MONTH << "]" << std::endl;
		return;
	}
	findCheapest("", month);
}

void FlightDatabase::saveData() const
{
	Poco::Path path("data");
	Poco::File dir(path);
	dir.createDirectories();

	for (auto& flight : flights)
	{
		Poco::File dataFile(Poco::Path(path, flight.first));
		dataFile.createFile();

		std::ofstream file(dataFile.path());
		if (file.is_open())
		{
			file << "# date\t:\tprice\t:\tcurrency\n";
			for (auto& flightInfo : flight.second)
			{
				file << flightInfo.first << " : " << flightInfo.second.getPrice()
						<< " : " << flightInfo.second.getCurrency() << "\n";
			}
			file.close();
		}
		else
		{
			std::cout << "Unable to open: " << dataFile.path() << std::endl;
		}
	}
}

void FlightDatabase::loadFromFS()
{
	Poco::Path path("data");
	Poco::File dir(path);

	std::vector<std::string> files;
	dir.list(files);

	flights.clear();

	for (std::string fileName : files)
	{
		Poco::Path p (path);
		p.append(Poco::Path(fileName));

		std::ifstream file(p.toString());
		if (file.is_open())
		{
			std::string str;
			std::map<std::string, Flight> fileFlights;
			bool oldEntry = true;
			while (std::getline(file, str))
			{
				if (str.find("#") != std::string::npos)
				{
					continue;
				}
				Poco::StringTokenizer t(str, ":",
						Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM);

				if (t.count() != 3)
				{
					std::cout << "Error:" << str << std::endl;
					continue;
				}
				if (oldEntry)
				{
					Poco::DateTime dt;
					int dummy;
					Poco::DateTimeParser::parse(t[0], dt, dummy);
					Poco::DateTime now;
					if (dt < now)
					{
						Poco::AutoPtr<Poco::SimpleFileChannel> pChannel(new Poco::SimpleFileChannel);
						pChannel->setProperty("path", "!expired_flights");
						pChannel->setProperty("rotation", "40K");
						Poco::Logger::root().setChannel(pChannel);
						Poco::Logger& logger = Poco::Logger::get("Expired Logger");

						logger.information(fileName + " on " + t[0] +
								" for " + t[1] + t[2] + " has expired");
						continue;
					}
					oldEntry = false;
				}


				fileFlights[t[0]] = Flight(Poco::NumberParser::parseFloat(t[1]), t[2]);
			}
			if (fileFlights.empty() == false)
			{
				flights[fileName] = fileFlights;
			}
			file.close();
		}
		else
		{
			std::cout << "Unable to open: " << p.toString() << std::endl;
		}
	}
}

